#include "BBN.h"
#include "SDL_gpu.h"
#include "NFont.h"
#include <cstdio>
#include <cstdarg>
#include <cstdlib>
#include <iostream>
#include <fstream>

namespace BBN
{



// Private hidden data to abstract away SDL_gpu
class ImageData
{
public:
    GPU_Image* image;
    
    ImageData()
        : image(NULL)
    {}
    ImageData(const string& filename)
    {
        image = GPU_LoadImage(filename.c_str());
    }
    ImageData(unsigned int width, unsigned int height)
    {
        image = GPU_CreateImage(width, height, 4);
    }
    
    ~ImageData()
    {
        GPU_FreeImage(image);
    }
};

// Private hidden data to abstract away NFont
class FontData
{
public:
    NFont* font;
    
    FontData()
        : font(NULL)
    {}
    FontData(const string& filename, unsigned int point_size)
    {
        font = new NFont;
        font->load(filename.c_str(), point_size, NFont::Color(255,255,255));
    }
    
    ~FontData()
    {
        delete font;
    }
};


// Private hidden data to abstract away SDL_gpu
class TargetData
{
public:
    GPU_Target* target;
    
    TargetData()
        : target(NULL)
    {}
    TargetData(GPU_Image* image)
        : target(NULL)
    {
        target = GPU_LoadTarget(image);
    }
};






Color::Color()
    : r(0), g(0), b(0), a(255)
{}
Color::Color(unsigned char r, unsigned char g, unsigned char b)
    : r(r), g(g), b(b), a(255)
{}
Color::Color(unsigned char r, unsigned char g, unsigned char b, unsigned char a)
    : r(r), g(g), b(b), a(a)
{}

Color& Color::rgb(unsigned char r, unsigned char g, unsigned char b)
{
    this->r = r;
    this->g = g;
    this->b = b;

    return *this;
}

Color& Color::rgba(unsigned char r, unsigned char g, unsigned char b, unsigned char a)
{
    this->r = r;
    this->g = g;
    this->b = b;
    this->a = a;

    return *this;
}

Color& Color::rgb_hex(Uint32 hex)
{
    Uint32 rmask, gmask, bmask;
    Uint8 rshift, gshift, bshift;
    rmask = 0xff0000;
    gmask = 0x00ff00;
    bmask = 0x0000ff;
    rshift = 16;
    gshift = 8;
    bshift = 0;
    
    this->r = (hex & rmask) >> rshift;
    this->g = (hex & gmask) >> gshift;
    this->b = (hex & bmask) >> bshift;
    this->a = 255;

    return *this;
}

Color& Color::rgba_hex(Uint32 hex)
{
    Uint32 rmask, gmask, bmask, amask;
    Uint8 rshift, gshift, bshift, ashift;
    rmask = 0xff000000;
    gmask = 0x00ff0000;
    bmask = 0x0000ff00;
    amask = 0x000000ff;
    rshift = 24;
    gshift = 16;
    bshift = 8;
    ashift = 0;
    
    this->r = (hex & rmask) >> rshift;
    this->g = (hex & gmask) >> gshift;
    this->b = (hex & bmask) >> bshift;
    this->a = (hex & amask) >> ashift;

    return *this;
}


Color& Color::setR(unsigned char r)
{
    this->r = r;
    return *this;
}
Color Color::newR(unsigned char r) const
{
    return Color(r, g, b, a);
}

Color& Color::setG(unsigned char g)
{
    this->g = g;
    return *this;
}
Color Color::newG(unsigned char g) const
{
    return Color(r, g, b, a);
}

Color& Color::setB(unsigned char b)
{
    this->b = b;
    return *this;
}
Color Color::newB(unsigned char b) const
{
    return Color(r, g, b, a);
}

Color& Color::setA(unsigned char a)
{
    this->a = a;
    return *this;
}
Color Color::newA(unsigned char a) const
{
    return Color(r, g, b, a);
}

Color Color::add_clamped(int r, int g, int b, int a) const
{
    Color c = *this;
    
    if(r > 255 - c.r)  // Adding clamp
        c.r = 255;
    else if(-r > c.r)  // Subtracting clamp
        c.r = 0;
    else
        c.r += r;  // Normal
    
    if(g > 255 - c.g)  // Adding clamp
        c.g = 255;
    else if(-g > c.g)  // Subtracting clamp
        c.g = 0;
    else
        c.g += g;  // Normal
        
    if(b > 255 - c.b)  // Adding clamp
        c.b = 255;
    else if(-b > c.b)  // Subtracting clamp
        c.b = 0;
    else
        c.b += b;  // Normal
        
    if(a > 255 - c.a)  // Adding clamp
        c.a = 255;
    else if(-a > c.a)  // Subtracting clamp
        c.a = 0;
    else
        c.a += a;  // Normal
    
    return c;
}

#define MIN3(a,b,c) ((a)<(b) && (a)<(c)? (a) : ((b)<(c)? (b) : (c)))
#define MAX3(a,b,c) ((a)>(b) && (a)>(c)? (a) : ((b)>(c)? (b) : (c)))

Color Color::rgb_to_hsv() const
{
	float r = this->r/255.0f;
	float g = this->g/255.0f;
	float b = this->b/255.0f;

	float h, s, v;

	float min, max, delta;
	min = MIN3( r, g, b );
	max = MAX3( r, g, b );

	v = max;				// v
	delta = max - min;
	if( max != 0 && min != max)
	{
		s = delta / max;		// s

		if( r == max )
			h = ( g - b ) / delta;		// between yellow & magenta
		else if( g == max )
			h = 2 + ( b - r ) / delta;	// between cyan & yellow
		else
			h = 4 + ( r - g ) / delta;	// between magenta & cyan
		h *= 60;				// degrees
		if( h < 0 )
			h += 360;
	}
	else {
		// r = g = b = 0		// s = 0, v is undefined
		s = 0;
		h = 0;// really undefined: -1
	}

    return Color(h * 256.0f/360.0f, s * 255, v * 255, a);
}

Color Color::hsv_to_rgb() const
{
    float h = r/255.0f;
    float s = g/255.0f;
    float v = b/255.0f;

    int H = floor(h*5.999f);
    float chroma = v*s;
    float x = chroma * (1 - fabs(fmod(h*5.999f, 2) - 1));

    unsigned char R = 0, G = 0, B = 0;
    switch(H)
    {
        case 0:
            R = 255*chroma;
            G = 255*x;
        break;
        case 1:
            R = 255*x;
            G = 255*chroma;
        break;
        case 2:
            G = 255*chroma;
            B = 255*x;
        break;
        case 3:
            G = 255*x;
            B = 255*chroma;
        break;
        case 4:
            R = 255*x;
            B = 255*chroma;
        break;
        case 5:
            R = 255*chroma;
            B = 255*x;
        break;
    }

    unsigned char m = 255*(v - chroma);

    return Color(R+m, G+m, B+m, a);
}

Uint32 Color::to_format(SDL_PixelFormat* format) const
{
    return SDL_MapRGBA(format, r, g, b, a);
}

SDL_Color Color::to_SDL_Color() const
{
    SDL_Color c = {r, g, b, a};
    return c;
}


std::string toString(unsigned int n)
{
    char buf[20];
    sprintf(buf, "%u", n);
    return buf;
}

std::string Color::to_string() const
{
	if(a == 255)
        return "(" + toString(r) + "," + toString(g) + "," + toString(b) + ")";
    else
        return "(" + toString(r) + "," + toString(g) + "," + toString(b) + "," + toString(a) + ")";
}

bool Color::operator==(const Color& color) const
{
    return (r == color.r && g == color.g && b == color.b && a == color.a);
}

bool Color::operator!=(const Color& color) const
{
    return (r != color.r || g != color.g || b != color.b || a != color.a);
}















Image::Image(unsigned int width, unsigned int height)
{
    data = new ImageData(width, height);
}

Image::Image(const string& filename)
{
    data = new ImageData(filename);
}

Image::Image()
{
    data = new ImageData;
}

Image::~Image()
{
    delete (ImageData*)data;
}

bool Image::failed() const
{
    GPU_Image* _image = ((ImageData*)data)->image;
    return (_image == NULL);
}

unsigned int Image::getWidth() const
{
    GPU_Image* _image = ((ImageData*)data)->image;
    if(_image == NULL)
        return 0;
    return _image->w;
}

unsigned int Image::getHeight() const
{
    GPU_Image* _image = ((ImageData*)data)->image;
    if(_image == NULL)
        return 0;
    return _image->h;
}
    
Image Image::copy() const
{
    GPU_Image* _image = ((ImageData*)data)->image;
    
    Image img;
    GPU_Image*& _image_copy = ((ImageData*)img.data)->image;
    _image_copy = GPU_CopyImage(_image);
    return img;
}








Point::Point(float x, float y)
    : x(x), y(y)
{}

void Point::draw(const Target& target, const Color& color) const
{
    GPU_Target* _target = ((TargetData*)target.data)->target;
    GPU_Pixel(_target, x, y, color.to_SDL_Color());
}



Line::Line(float x1, float y1, float x2, float y2)
    : x1(x1), y1(y1), x2(x2), y2(y2)
{}

void Line::draw(const Target& target, const Color& color) const
{
    GPU_Target* _target = ((TargetData*)target.data)->target;
    GPU_Line(_target, x1, y1, x2, y2, color.to_SDL_Color());
}



Rectangle::Rectangle(float x1, float y1, float x2, float y2)
    : x1(x1), y1(y1), x2(x2), y2(y2)
{}

void Rectangle::draw(const Target& target, const Color& color) const
{
    GPU_Target* _target = ((TargetData*)target.data)->target;
    GPU_Rect(_target, x1, y1, x2, y2, color.to_SDL_Color());
}

void Rectangle::fill(const Target& target, const Color& color) const
{
    GPU_Target* _target = ((TargetData*)target.data)->target;
    GPU_RectFilled(_target, x1, y1, x2, y2, color.to_SDL_Color());
}



RoundedRectangle::RoundedRectangle(float x1, float y1, float x2, float y2, float radius)
    : x1(x1), y1(y1), x2(x2), y2(y2), radius(radius)
{}

void RoundedRectangle::draw(const Target& target, const Color& color) const
{
    GPU_Target* _target = ((TargetData*)target.data)->target;
    GPU_RectRound(_target, x1, y1, x2, y2, radius, color.to_SDL_Color());
}

void RoundedRectangle::fill(const Target& target, const Color& color) const
{
    GPU_Target* _target = ((TargetData*)target.data)->target;
    GPU_RectRoundFilled(_target, x1, y1, x2, y2, radius, color.to_SDL_Color());
}



Circle::Circle(float x, float y, float radius)
    : x(x), y(y), radius(radius)
{}

void Circle::draw(const Target& target, const Color& color) const
{
    GPU_Target* _target = ((TargetData*)target.data)->target;
    GPU_Circle(_target, x, y, radius, color.to_SDL_Color());
}

void Circle::fill(const Target& target, const Color& color) const
{
    GPU_Target* _target = ((TargetData*)target.data)->target;
    GPU_CircleFilled(_target, x, y, radius, color.to_SDL_Color());
}








Font::Font(const string& filename, unsigned int point_size)
{
    data = new FontData(filename, point_size);
}

Font::Font()
{
    data = new FontData;
}

Font::~Font()
{
    delete (FontData*)data;
}

bool Font::failed() const
{
    NFont* _font = ((FontData*)data)->font;
    return (_font == NULL);
}

unsigned int Font::getHeight() const
{
    NFont* _font = ((FontData*)data)->font;
    if(_font == NULL)
        return 0;
    return _font->getHeight();
}

unsigned int Font::getWidth(const string& text) const
{
    NFont* _font = ((FontData*)data)->font;
    if(_font == NULL)
        return 0;
    return _font->getWidth("%s", text.c_str());
}

unsigned int Font::getHeight(const string& text) const
{
    NFont* _font = ((FontData*)data)->font;
    if(_font == NULL)
        return 0;
    return _font->getHeight("%s", text.c_str());
}







Target::Target(const Image& image)
{
    data = new TargetData(((ImageData*)image.data)->image);
}

Target::Target()
{
    data = new TargetData();
}

Target::~Target()
{
    delete (TargetData*)data;
}

unsigned int Target::getWidth() const
{
    GPU_Target* target = ((TargetData*)data)->target;
    if(target == NULL)
        return 0;
    return target->w;
}

unsigned int Target::getHeight() const
{
    GPU_Target* target = ((TargetData*)data)->target;
    if(target == NULL)
        return 0;
    return target->h;
}

void Target::clear()
{
    GPU_Target* target = ((TargetData*)data)->target;
    GPU_Clear(target);
}

void Target::clear(Uint8 r, Uint8 g, Uint8 b, Uint8 a)
{
    GPU_Target* target = ((TargetData*)data)->target;
    GPU_ClearRGBA(target, r, g, b, a);
}

void Target::draw(const Image& image, float x, float y)
{
    GPU_Target* target = ((TargetData*)data)->target;
    GPU_Image* _image = ((ImageData*)image.data)->image;
    GPU_Blit(_image, NULL, target, x, y);
}

void Target::draw(const Image& image, float x, float y, float angle)
{
    GPU_Target* target = ((TargetData*)data)->target;
    GPU_Image* _image = ((ImageData*)image.data)->image;
    GPU_BlitRotate(_image, NULL, target, x, y, angle);
}

void Target::draw(const Image& image, float x, float y, float scale_x, float scale_y)
{
    GPU_Target* target = ((TargetData*)data)->target;
    GPU_Image* _image = ((ImageData*)image.data)->image;
    GPU_BlitScale(_image, NULL, target, x, y, scale_x, scale_y);
}

void Target::draw(const Image& image, float x, float y, float angle, float scale_x, float scale_y)
{
    GPU_Target* target = ((TargetData*)data)->target;
    GPU_Image* _image = ((ImageData*)image.data)->image;
    GPU_BlitTransform(_image, NULL, target, x, y, angle, scale_x, scale_y);
}

void Target::draw(const Shape& shape, const Color& color) const
{
    shape.draw(*this, color);
}

void Target::fill(const Shape& shape, const Color& color) const
{
    shape.fill(*this, color);
}

void Target::draw(const Font& font, const string& text, float x, float y, const Color& color)
{
    GPU_Target* target = ((TargetData*)data)->target;
    NFont* _font = ((FontData*)font.data)->font;
    if(_font == NULL)
        return;
    
    GPU_SetRGBA(color.r, color.g, color.b, color.a);
    _font->draw(target, x, y, "%s", text.c_str());
    GPU_SetColor(NULL);
}




Screen::Screen(unsigned int width, unsigned int height)
    : Target()
{
    initialize(width, height);
}

Screen::~Screen()
{
    shut_down();
}

bool Screen::failed() const
{
    GPU_Target* target = ((TargetData*)data)->target;
    return (target == NULL);
}

void Screen::show() const
{
    GPU_Flip();
}

bool Screen::initialize(unsigned int width, unsigned int height)
{
    GPU_Target*& target = ((TargetData*)data)->target;
    target = GPU_Init(NULL, width, height, 0);
    return (target == NULL);
}
void Screen::shut_down()
{
    GPU_Quit();
}









float get_total_time()
{
    return SDL_GetTicks()/1000.0f;
}

void print_line(const string& str)
{
    GPU_LogError("%s\n", str.c_str());
}

void wait(float seconds)
{
    SDL_Delay(seconds*1000);
}


}
